import {DBManager, Storage} from "mvx-foundation";
import {Async, Millisecond} from "mvx-utils";

class WXStorage implements Storage {
  constructor() {
    this.cache_ = new Map<string, Map<string, string>>([]);
    wx.onAppHide(async () => {
      for (let [table, {timerId}] of this.savingTimer) {
        await this.saveToStorage(table);
        clearTimeout(timerId);
      }
      this.savingTimer.clear();
    })
  }

  async loadFromStorage(table:string):Promise<Map<string, string>> {
    if (this.cache_.has(table)) {
      return this.cache_.get(table)!;
    }

    if (!this.loadWaiting.has(table)) {
      this.loadWaiting.set(table, []);
    }

    return new Promise(resolve => {
      const waiting = this.loadWaiting.get(table)!;
      waiting.push(resolve);
      if (waiting.length>1) {
        return ;
      }

      Async(async () => {
        try {
          const ret = await wx.getStorage({key:encodeURIComponent(table)});
          const map = new Map<string, string>(ret.data);
          this.cache_.set(table, map);
        } catch (e) {
          this.cache_.set(table, new Map<string, string>([]));
        }

        for (let r of waiting) {
          r(this.cache_.get(table)!);
        }
        this.loadWaiting.set(table, [])
      })
    })
  }

  private async saveToStorage(table:string):Promise<Error|null> {
    try {
      const map = await this.loadFromStorage(table);
      await wx.setStorage({key:encodeURIComponent(table), data:Array.from(map.entries())});
      return null;
    } catch (e) {
      return new Error("failed");
    }
  }

  async get(table:string, key: string): Promise<[string, Error | null]> {
    const map = await this.loadFromStorage(table);
    if (map.has(key)) {
      return [map.get(key)!, null]
    }

    return ["", new Error(`not found`)]
  }

  async remove(table:string, key: string): Promise<Error | null> {
    const item = await this.loadFromStorage(table);
    item.delete(key);
    return await this.saveToStorage(table);
  }

  async set(table:string, key: string, value: string): Promise<Error | null> {
    const item = await this.loadFromStorage(table);
    item.set(key, value);

    if (this.savingTimer.has(table)) {
      clearTimeout(this.savingTimer.get(table)!.timerId);
      this.savingTimer.delete(table);
    }

    const timerId = setTimeout(async () => {
      await this.saveToStorage(table);
      this.savingTimer.delete(table);
    }, 100*Millisecond);
    this.savingTimer.set(table, {timerId:timerId});

    return null;
  }

  private loadWaiting:Map<string, ((value:Map<string, string>) => void)[]> = new Map([]);
  private cache_:Map<string, Map<string, string>>;
  private savingTimer:Map<string, {timerId:number}> = new Map([]);
}

export class WXDBManager extends DBManager {
  protected newSessionStorage():Storage {
    return new WXStorage();
  }

  protected newLocalStorage():Storage {
    return new WXStorage();
  }
}